﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using LINQPad.OpenAccess.Helpers;
using Telerik.OpenAccess;
using Telerik.OpenAccess.Metadata;

namespace LINQPad.OpenAccess.Metadata.Reflection.Adapters
{
    /// <summary>
    /// Reflection based <see cref="IContextTypeInfo"/> adapter.
    /// </summary>
    public sealed class ContextTypeInfoAdapter : IContextTypeInfoAdapter
    {
        [DebuggerBrowsable( DebuggerBrowsableState.Never )]
        private static readonly Type[] _CustomConstructorParameterTypes = new Type[] { typeof( string ), typeof( BackendConfiguration ), typeof( MetadataSource ) };

        #region Private

        #region - Functions

        /// <summary>
        /// Gets the default backend configuration.
        /// </summary>
        /// <param name="contextType">Type of the context.</param>
        /// <returns>
        /// The default backend configuration or <c>null</c>.
        /// </returns>
        private BackendConfiguration GetDefaultBackendConfiguration( Type contextType )
        {
            var methodInfo = contextType.GetMethod
                                            (
                                                "GetBackendConfiguration"
                                                , BindingFlags.Static | BindingFlags.Public
                                                , null
                                                , Type.EmptyTypes
                                                , null
                                            );

            if ( methodInfo == null || !typeof( BackendConfiguration ).IsAssignableFrom( methodInfo.ReturnType ) )
                return null;
            return ( BackendConfiguration ) methodInfo.Invoke( null, new object[0] );
        }
        /// <summary>
        /// Gets the default metadata source.
        /// </summary>
        /// <param name="contextType">Type of the context.</param>
        /// <returns>The default metadata source or <c>null</c>.</returns>
        private MetadataSource GetDefaultMetadataSource( Type contextType )
        {
            var fieldAccessor = contextType.GetField( "metadataSource", BindingFlags.Static | BindingFlags.NonPublic );
            if ( fieldAccessor == null || !typeof( MetadataSource ).IsAssignableFrom( fieldAccessor.FieldType ) )
                return null;
            return ( MetadataSource ) fieldAccessor.GetValue( null );
        }
        /// <summary>
        /// Determines whether the parameter collection matches the required one for the custom constructor.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        /// <returns>
        ///   <c>true</c> on match; otherwise, <c>false</c>.
        /// </returns>
        private bool MatchesRequiredParametersForCustomConstructor( ParameterInfo[] parameters )
        {
            if ( parameters == null || parameters.Length != _CustomConstructorParameterTypes.Length )
                return false;

            for ( int i = 0; i < _CustomConstructorParameterTypes.Length; i++ )
                for ( int j = 0; j < parameters.Length; j++ )
                {
                    if ( _CustomConstructorParameterTypes[i] == parameters[j].ParameterType )
                        break;
                    if ( j == parameters.Length - 1 )
                        return false;
                }
            return true;
        }

        #endregion

        #endregion

        #region IContextTypeInfoAdapter Members

        /// <summary>
        /// Adapts the specified source to a <see cref="IContextTypeInfo" /> instance.
        /// </summary>
        /// <param name="contextType">Type of the context.</param>
        /// <returns>
        /// The <see cref="IContextTypeInfo" />.
        /// </returns>
        public IContextTypeInfo Adapt( Type contextType )
        {
            Guard.ArgumentNotNull( contextType, "contextType" );

            var backendConfiguration = this.GetDefaultBackendConfiguration( contextType );
            var metadataSource = this.GetDefaultMetadataSource( contextType );


            var hasCustomConstructor = contextType.GetConstructors( )
                                                .Select( c => new { Ctor = c, Parameters = c.GetParameters( ) } )
                                                .Where( c => MatchesRequiredParametersForCustomConstructor( c.Parameters ) )
                                                .Any( );

            return new ContextTypeInfo
                        (
                            contextType
                            , backendConfiguration
                            , metadataSource
                            , hasDefaultConstructor: contextType.GetConstructor( Type.EmptyTypes ) != null
                            , hasCustomConstructor: hasCustomConstructor
                        );
        }

        #endregion
    }
}